package com.bkhech;

import cn.hutool.setting.dialect.Props;
import com.bkhech.netty.nacos.NacosClient;
import com.bkhech.netty.rabbitmq.RabbitMQConnectUtil;
import com.bkhech.netty.utils.JedisPoolUtil;
import com.bkhech.netty.utils.PropertiesUtil;
import com.bkhech.netty.utils.ZKUtil;
import com.bkhech.netty.websocket.WSServerInitializer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import redis.clients.jedis.Jedis;

import java.util.List;
import java.util.Map;

import static com.bkhech.RedisKeyConstant.NETTY_PORT_KEY;
import static com.bkhech.RedisKeyConstant.NETTY_QUEUE_NAME_PREFIX;
import static com.bkhech.ZkConstant.NODE_NAME;

/**
 * ChatServer
 *  聊天服务启动类
 * @author guowm
 * @date 2024-11-20
 */
@Slf4j
public class ChatServer2 {
    private static final int initOnlineCounts = 0;
    private static final Props properties = PropertiesUtil.getInstance();

    public static void main(String[] args) {
        int defaultPort = PropertiesUtil.getDefaultPort();
        if (args != null && args.length > 0) {
            defaultPort = Integer.parseInt(args[0]);
            log.info("++++++++++++ defaultPort is {} +++++++++++++", defaultPort);
        }

        // 定义主线程组，用于接收客户端的连接
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        // 定义从线程组，即工作线程组，处理主线程池交过来的业务任务
        EventLoopGroup workGroup = new NioEventLoopGroup();
        // netty启动的时候，从redis查找有没有端口，如果没有则使用默认的875端口，如果存在端口，则从最大的端口开始累加10
        int nettPort = selectPort(defaultPort);

        try {
            // 把 chat-server 注册到 zookeeper
            ZKUtil.registerNettyServer(NODE_NAME, ZKUtil.getLocalIp(), nettPort);
            // 启动消费者进行监听，队列可以根据动态生成的端口进行动态拼接
            String queueName = NETTY_QUEUE_NAME_PREFIX + nettPort;
            RabbitMQConnectUtil rabbitMQConnectUtil = new RabbitMQConnectUtil();
            rabbitMQConnectUtil.listen(properties.getProperty("rabbitmq.chat.message.fanout.exchange"), queueName);
            log.info("mq 开启监听......");
            // 构建netty服务器
            // 服务的启动类
            ServerBootstrap server = new ServerBootstrap();
            // 把主从线程池组放入到启动类中
            server.group(bossGroup, workGroup)
                    // 设置Nio的双向通道
                    .channel(NioServerSocketChannel.class)
                    // 设置处理器，用于处理workerGroup
                    .childHandler(new WSServerInitializer());

            // 启动 server，并且绑定端口号，同时启动方式为“同步”
            ChannelFuture channelFuture = server.bind(nettPort).sync();
            // 监听关闭的channel
            channelFuture.channel().closeFuture().sync();
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }

    /**
     *
     *  netty启动的时候，从redis查找有没有端口，如果没有则使用默认的875端口，
     *  如果没有注册中心则不能使用动态端口，因为一旦端口动态，可能会导致传入的端口和实际传入的端口不一致，
     *  导致外部（nginx 配置 upstream）无法配置准确的端口。
     *
     *  存在的问题：
     *   1.监听服务必须高可用，如果 Netty 服务下线的时候，监听服务不在线，会导致无法删除残留数据的问题
     * @param defaultPort
     * @return
     */
    public static int selectPort(int defaultPort) {
        final String portKey = NETTY_PORT_KEY;
        Map<String, String> map = JedisPoolUtil.executeInJedis(jedis -> jedis.hgetAll(portKey));

        List<Integer> portList = map.keySet().stream()
                .map(Integer::parseInt).toList();
        System.out.println("selectPort portList = " + portList);
        // netty启动的时候，从redis查找有没有端口，如果没有则使用默认的875端口，如果存在端口，则从最大的端口开始累加10
        int nettyPort = -1;
        if (CollectionUtils.isEmpty(portList) || !portList.contains(defaultPort)) {
            nettyPort = defaultPort;
            final int nettyPortFinal = nettyPort;
            JedisPoolUtil.executeInJedis(jedis -> jedis.hset(portKey, nettyPortFinal + "", initOnlineCounts + ""));
        } else {
            // 如果存在重复端口，直接抛异常，启动失败。
            throw new RuntimeException("请检查端口 " + defaultPort + " 是否重复");
        }
        return nettyPort;
    }

    /**
     *
     *  netty启动的时候，从redis查找有没有端口，如果没有则使用默认的875端口，如果存在端口，则从最大的端口开始累加10
     *
     *  存在的问题：
     *   1.监听服务必须高可用，如果 Netty 服务下线的时候，监听服务不在线，会导致无法删除残留数据的问题
     * @param defaultPort
     * @return
     */
    public static int selectPortInRegistry(int defaultPort) {
        final String portKey = NETTY_PORT_KEY;
        Map<String, String> map = JedisPoolUtil.executeInJedis(jedis -> jedis.hgetAll(portKey));

        List<Integer> portList = map.keySet().stream()
                .map(Integer::parseInt).toList();
        System.out.println("selectPortInRegistry portList = " + portList);
        // netty启动的时候，从redis查找有没有端口，如果没有则使用默认的875端口，如果存在端口，则从最大的端口开始累加10
        int nettyPort = -1;
        if (CollectionUtils.isEmpty(portList)) {
            nettyPort = defaultPort;
            final int nettyPortFinal = nettyPort;
            JedisPoolUtil.executeInJedis(jedis -> jedis.hset(portKey, nettyPortFinal + "", initOnlineCounts + ""));
        } else {
            // 如果存在端口，则从最大的端口开始累加10
            int currPort = portList.stream().max(Integer::compareTo).get() + 10;
            final int nettyPortFinal = currPort;
            JedisPoolUtil.executeInJedis(jedis -> jedis.hset(portKey, nettyPortFinal + "", initOnlineCounts + ""));
            nettyPort = currPort;
        }
        return nettyPort;
    }

}